//
//  KLEngine.m
//  WebRTCDemo_iOS
//
//  Created by szblsx2 on 2020/3/5.
//  Copyright © 2020 szblsx2. All rights reserved.
//

#import "KLEngine.h"
#import "KLPeerLocal.h"
#import "KLPeerRemote.h"
#import "KLLocalVideoCell.h"
#import "KLRemoteVideoCell.h"
#import "Reachability.h"

static NSString *kLocalVideoCellIdentifier = @"kLocalVideoCellIdentifier";
static NSString *kRemoteVideoCellIdentifier = @"kRemoteVideoCellIdentifier";

@interface KLEngine ()<KLClientDelegate,KLPeerLocalDelegate,KLPeerRemoteDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>
//昵称
@property (nonatomic, copy) NSString *name;
//服务器地址
@property (nonatomic, copy) NSString *server;
//端口号
@property (nonatomic, copy) NSString *port;
//房间id
@property (nonatomic, copy) NSString *roomId;
//信令对象
@property (nonatomic, strong) KLClient *mKLClient;
//连接工厂
@property (nonatomic, strong) RTCPeerConnectionFactory *mPeerConnectionFactory;
//ice服务器地址
@property (nonatomic, strong) NSMutableArray <RTCIceServer *> *iceServers;
//本端连接
@property (nonatomic, strong) KLPeerLocal *mKLPeerLocal;
//远端连接集合
@property (nonatomic, strong) NSMutableArray <KLPeerRemote *> *mKLPeerRemotes;
//视频渲染器
@property (nonatomic, weak) UICollectionView *videoRenderer;
//本地视频源
@property (nonatomic, strong) AVCaptureSession *captureSession;
//远程视频追踪
@property (nonatomic, strong) NSMutableDictionary <NSString *, RTCVideoTrack *> *remoteVideoTracks;
//网络监听
@property (nonatomic, strong) Reachability *reachability;
//网络状态
@property (nonatomic, assign) NetworkStatus netStatus;
//socket连接状态码
@property (nonatomic, assign) NSInteger socketCode;
//断线重连定时器
@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation KLEngine
{
    //临时变量 是否开启扬声器播放
    BOOL _openSpeaker;
    //临时变量 是否离开会议
    BOOL _bCloseMeet;
}

/** 初始化连接器*/
- (void)initEngineWithServer:(NSString *)server port:(NSString *)port name:(NSString *)name
{
    _server = server ?: @"";
    _port = port ?: @"";
    _name = name ?: @"";
    _roomId = @"";
    _openSpeaker = YES;
}

/**设置turn转发服务器地址*/
- (void)setupIceServers:(NSArray<RTCIceServer *> *)iceServers
{
    if (iceServers.count > 0) {
        [_iceServers removeAllObjects];
        [_iceServers addObjectsFromArray:iceServers];
    }
}

/**设置视频渲染窗口*/
- (void)setupVideoRenderer:(UICollectionView *)videoRenderer
{
    _videoRenderer = videoRenderer;
    [_videoRenderer registerClass:[KLLocalVideoCell class] forCellWithReuseIdentifier:kLocalVideoCellIdentifier];
    [_videoRenderer registerClass:[KLRemoteVideoCell class] forCellWithReuseIdentifier:kRemoteVideoCellIdentifier];
    _videoRenderer.dataSource = self;
    _videoRenderer.delegate = self;
}

/**创建会议*/
- (void)createMeetingWithRoomId:(NSString *)roomId
{
    __weak __typeof(self) ws = self;
    [self connectSocketComplete:^(BOOL status) {
        if (!status) {
            return;
        }
        self->_bCloseMeet = NO;
        ws.roomId = roomId;
        //添加网络监听
        [self addNetStatusObserver];
        //发送加入房间信令
        [ws.mKLClient sendJoin:ws.roomId info:@{@"name" : ws.name}];
        //创建上行Peer
        ws.mKLPeerLocal = [[KLPeerLocal alloc] initWithClient:ws.mKLClient factory:ws.mPeerConnectionFactory iceServers:ws.iceServers];
        ws.mKLPeerLocal.roomId = ws.roomId;
        ws.mKLPeerLocal.delegate = self;
        [ws.mKLPeerLocal freePeerConnection];
        [ws.mKLPeerLocal initCapturer];
        [ws.mKLPeerLocal initPeerConnection];
        //回调上层
        NSDictionary *responeDict = @{@"event":@(2000),@"code":@(200),@"mid":@"",@"roomId":ws.roomId};
        if (ws.eventHandleBlock) {
            ws.eventHandleBlock(responeDict);
        }
        
        [ws.mKLPeerLocal createOffer];
        ws.mKLPeerLocal.eventHandleBlock = ^(NSDictionary * _Nonnull response) {
            //解析response 拦截peer断线事件
//            NSInteger event = [response[@"event"] integerValue];
//            NSInteger code = [response[@"code"] integerValue];
//            if (event == 1002 && code == 201 && !self->_bCloseMeet) {
//                [ws startTimer];
//            }else{
//                [ws stopTimer];
//            }
            //回调上层
            if (ws.eventHandleBlock) {
                ws.eventHandleBlock(response);
            }
        };
        //设置屏幕常亮
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
    }];
    
}

/**加入会议*/
- (void)joinMeetingWithRoomId:(NSString *)roomId
{
    __weak __typeof(self) ws = self;
    [self connectSocketComplete:^(BOOL status) {
        if (!status) {
            return;
        }
        self->_bCloseMeet = NO;
        ws.roomId = roomId;
        //添加网络监听
        [self addNetStatusObserver];
        //发送加入房间信令
        [ws.mKLClient sendJoin:ws.roomId info:@{@"name" : ws.name}];
        //创建上行Peer
        ws.mKLPeerLocal = [[KLPeerLocal alloc] initWithClient:ws.mKLClient factory:ws.mPeerConnectionFactory iceServers:ws.iceServers];
        ws.mKLPeerLocal.roomId = ws.roomId;
        ws.mKLPeerLocal.delegate = self;
        [ws.mKLPeerLocal freePeerConnection];
        [ws.mKLPeerLocal initCapturer];
        [ws.mKLPeerLocal initPeerConnection];
        //回调上层
        NSDictionary *responeDict = @{@"event":@(2001),@"code": @(200),@"mid":@"",@"roomId":ws.roomId};
        if (ws.eventHandleBlock) {
            ws.eventHandleBlock(responeDict);
        }
        [ws.mKLPeerLocal createOffer];
        ws.mKLPeerLocal.eventHandleBlock = ^(NSDictionary * _Nonnull response) {
            //解析response 拦截peer断线事件
//            NSInteger event = [response[@"event"] integerValue];
//            NSInteger code = [response[@"code"] integerValue];
//            if (event == 1002 && code == 201 && !self->_bCloseMeet) {
//                [ws startTimer];
//            }else{
//                [ws stopTimer];
//            }
            //回调上层
            if (ws.eventHandleBlock) {
                ws.eventHandleBlock(response);
            }
        };
        //设置屏幕常亮
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
    }];
}

/**离开会议*/
- (void)leaveMeeting
{
    [self removeNetStatusObserver];
    _bCloseMeet = YES;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 释放上行Peer
        if (self->_mKLPeerLocal) {
            [self->_mKLPeerLocal sendUnPublish];
            [self->_mKLPeerLocal freePeerConnection];
            [self->_mKLPeerLocal freeCapture];
            self->_mKLPeerLocal = nil;
        }
        // 释放下行Peer
        for (KLPeerRemote *klPeerRemote in self.mKLPeerRemotes) {
            [klPeerRemote sendUnSubscribe];
            [klPeerRemote freePeerConnection];
        }
        [self->_mKLPeerRemotes removeAllObjects];
        [self->_remoteVideoTracks removeAllObjects];
        self->_captureSession = nil;
        // 发送退出信令
        if (self->_roomId) {
            //离开房间
            [self->_mKLClient sendLeave:self->_roomId];
            //关闭socket
            [self->_mKLClient close];
        }
        //回调上层
        NSDictionary *responeDict = @{@"event":@(2002),@"code":@(200),@"mid":@"",@"roomId":self->_roomId};
        if (self->_eventHandleBlock) {
            self->_eventHandleBlock(responeDict);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            //关闭屏幕常亮
            [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
        });
    });
}

/**释放工厂对象*/
- (void)freeSdk
{
    [self freePeerConnectFactory];
}

/**是否打开摄像头*/
- (void)turnOnCamera:(BOOL)open
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal turnOnCamera:open];
    }
}

/**切换前后置摄像头*/
- (void)switchCamera
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal switchCamera];
    }
}

/**是否打开麦克风*/
- (void)turnOnMicrophone:(BOOL)open
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal turnOnMicrophone:open];
    }
}

/**是否开启扬声器*/
- (void)whetherToOpenLoudspeaker:(BOOL)open
{
    if (_mKLPeerLocal) {
        _openSpeaker = open;
        [_mKLPeerLocal whetherToOpenLoudspeaker:open];
    }
}

- (void)test1
{
    if (self.mKLClient) {
        [self.mKLClient close];
    }
}


#pragma mark - KLPeerLocalDelegate
/**获取到本地视频源*/
- (void)klPeerLocal:(KLPeerLocal *)peerLocal capturerSession:(AVCaptureSession *)captureSession
{
    _captureSession = captureSession;
    if (_videoRenderer) {
        [self reloadVideoRender];
    }
}

#pragma mark - KLPeerRemoteDelegate
/**获取到远端视频流*/
- (void)klPeerRemote:(KLPeerRemote *)peerRemote addRemoteVideoTrack:(RTCVideoTrack *)videoTrack streamId:(NSString *)streamId
{
    if (_videoRenderer) {
        [_remoteVideoTracks setObject:videoTrack forKey:streamId];
        [self reloadVideoRender];
    }
}


#pragma mark - KLClientDelegate
/**处理发布流响应*/
- (void)klClient:(KLClient *)client respPublishWithMid:(NSString *)strMid answerSdp:(NSString *)sdp
{
    self.mKLPeerLocal.streamId = strMid;
    RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
    [self.mKLPeerLocal onRemoteDescription:sessionDescription];
    
}

/**处理订阅流响应*/
- (void)klClient:(KLClient *)client respSubscribeWithMid:(NSString *)strMid answerSdp:(NSString *)sdp
{
    for (KLPeerRemote *klPeerRemote in self.mKLPeerRemotes) {
        if ([klPeerRemote.streamId isEqualToString:strMid]) {
            RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
            [klPeerRemote onRemoteDescription:sessionDescription];
        }
    }
}

/**处理有人加入消息*/
- (void)klClient:(KLClient *)client respPeerJoinWithData:(NSDictionary *)data
{
    NSLog(@"用户加入了房间");
    //事件回调
    NSDictionary *responeDict = @{@"event":@(3000),@"code":@(200),@"mid":@"",@"roomId":_roomId};
    if (self.eventHandleBlock) {
        self.eventHandleBlock(responeDict);
    }
}

/**处理有人离开消息*/
- (void)klClient:(KLClient *)client respPeerLeaveWithData:(NSDictionary *)data
{
    NSLog(@"用户离开了房间");
    //事件回调
    NSDictionary *responeDict = @{@"event":@(3001),@"code":@(200),@"mid":@"",@"roomId":_roomId};
    if (self.eventHandleBlock) {
        self.eventHandleBlock(responeDict);
    }
}

/**处理有流加入消息*/
- (void)klClient:(KLClient *)client respStreamAddWithMid:(nonnull NSString *)strMid
{
    KLPeerRemote *klPeerRemote = [[KLPeerRemote alloc] initWithClient:self.mKLClient factory:self.mPeerConnectionFactory iceServers:self.iceServers];
    klPeerRemote.roomId = self.roomId;
    klPeerRemote.streamId = strMid;
    klPeerRemote.openSpeaker = _openSpeaker;
    klPeerRemote.delegate = self;
    [klPeerRemote initPeerConnection];
    [klPeerRemote createOffer];
    __weak __typeof(self) ws = self;
    klPeerRemote.eventHandleBlock = ^(NSDictionary * _Nonnull response) {
        if (ws.eventHandleBlock) {
            ws.eventHandleBlock(response);
        }
    };
    [self.mKLPeerRemotes addObject:klPeerRemote];
    //事件回调
    NSDictionary *responeDict = @{@"event":@(3002),@"code":@(200),@"mid":strMid,@"roomId":_roomId};
    if (self.eventHandleBlock) {
        self.eventHandleBlock(responeDict);
    }
}

/**处理有流移除消息*/
- (void)klClient:(KLClient *)client respStreamRemoveWithMid:(NSString *)strMid data:(NSDictionary *)data
{
    KLPeerRemote *mKLPeerRemote;
    for (KLPeerRemote *klPeerRemote in self.mKLPeerRemotes) {
        if ([klPeerRemote.streamId isEqualToString:strMid]) {
            mKLPeerRemote = klPeerRemote;
        }
    }
    if (mKLPeerRemote) {
        if (_videoRenderer) {
            [_remoteVideoTracks removeObjectForKey:strMid];
            [self reloadVideoRender];
            
        }
        [mKLPeerRemote freePeerConnection];
        [self.mKLPeerRemotes removeObject:mKLPeerRemote];
        NSLog(@"移除流成功 %@",strMid);
    }
    //事件回调
    NSDictionary *responeDict = @{@"event":@(3003),@"code":@(200),@"mid":strMid,@"roomId":_roomId};
    if (self.eventHandleBlock) {
        self.eventHandleBlock(responeDict);
    }
}

#pragma mark - UICollectionViewDelegate,UICollectionViewDataSource

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    NSInteger count = self.remoteVideoTracks.allValues.count;
    return _captureSession ? count + 1 : count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    if (_captureSession) {
        if (indexPath.row == 0) {
            KLLocalVideoCell *localCell = [collectionView dequeueReusableCellWithReuseIdentifier:kLocalVideoCellIdentifier forIndexPath:indexPath];
            localCell.captureSession = _captureSession;
            return localCell;
        }else{
            KLRemoteVideoCell *remoteCell = [collectionView dequeueReusableCellWithReuseIdentifier:kRemoteVideoCellIdentifier forIndexPath:indexPath];
            NSArray *videoTracksList = _remoteVideoTracks.allValues;
            remoteCell.videoTrack = videoTracksList[indexPath.row - 1];
            return remoteCell;
        }
    }else{
        KLRemoteVideoCell *remoteCell = [collectionView dequeueReusableCellWithReuseIdentifier:kRemoteVideoCellIdentifier forIndexPath:indexPath];
        NSArray *videoTracksList = _remoteVideoTracks.allValues;
        remoteCell.videoTrack = videoTracksList[indexPath.row];
        return remoteCell;
    }
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
    return 0;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
    return 0;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger count = self.remoteVideoTracks.allValues.count;
    NSInteger itemCount = _captureSession ? count + 1 : count;
    
    CGFloat cWidth = collectionView.frame.size.width;
    CGFloat cHeight = collectionView.frame.size.height;
    if (itemCount == 1)
    {
        return CGSizeMake(cWidth, cHeight);
    }
    else if (itemCount > 1 && itemCount <= 4)
    {
        return CGSizeMake(cWidth/2, cWidth/2);
    }
    
    return CGSizeMake(cWidth/3, cWidth/3);
}

#pragma mark - Private Method
/**开启连接*/
- (void)connectSocketComplete:(void (^)(BOOL))success
{
    //连接socket
    NSString *urlString = [NSString stringWithFormat:@"ws://%@:%@/ws?peer=%@",_server,_port,kDeviceUUID];
    [self.mKLClient startWithServerUrl:urlString success:^(BOOL status) {
        success(status);
    }];
}

/**释放连接工厂*/
- (void)freePeerConnectFactory
{
    if (_mPeerConnectionFactory) {
        _mPeerConnectionFactory = nil;
    }
}

/**视图刷新*/
- (void)reloadVideoRender{
    __weak __typeof(self) ws = self;
    [UIView animateWithDuration:0 animations:^{
        [ws.videoRenderer reloadData];
    }];
}

/**添加网络监听*/
- (void)addNetStatusObserver
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
    if (!_reachability) {
        _reachability = [Reachability reachabilityForInternetConnection];
    }
    [_reachability startNotifier];
    
    [self updateInterfaceWithReachability:_reachability];
}

- (void)reachabilityChanged:(NSNotification *)note
{
    Reachability *curReach = [note object];
    NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
    
    [self updateInterfaceWithReachability:curReach];
}

- (void)updateInterfaceWithReachability:(Reachability *)reachability
{
    NetworkStatus netStatus = [reachability currentReachabilityStatus];
    self.netStatus = netStatus;
    switch (netStatus) {
        case NotReachable:
        {
            NSLog(@"====当前网络状态不可用=======");
            [self stopTimer];
        }
            break;
        case ReachableViaWiFi:
        {
            NSLog(@"====当前网络状态为Wifi=======");
            if (!_bCloseMeet && _socketCode == 201) {
                [self startTimer];
            }else{
                [self stopTimer];
            }
        }
            break;
        case ReachableViaWWAN:
        {
            NSLog(@"====当前网络状态为流量=======");
            if (!_bCloseMeet && _socketCode == 201) {
                [self startTimer];
            }else{
                [self stopTimer];
            }
        }
            break;
    }
}

/**移除网络状况监听*/
- (void)removeNetStatusObserver
{
    if (_reachability) {
        [_reachability stopNotifier];
    }
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}

/**开启定时器*/
- (void)startTimer
{
    if (!_timer) {
        NSLog(@"开启定时器重连");
        //创建GCD定时器
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //每3秒执行
        dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 3.0 * NSEC_PER_SEC, 0);
        // 开启定时器
        dispatch_resume(_timer);
        //事件回调
        dispatch_source_set_event_handler(_timer, ^{
            [self reconnection];
        });
    }
    
}

/**关闭定时器*/
- (void)stopTimer
{
    if (_timer) {
        NSLog(@"关闭定时器");
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

/**断线重连*/
- (void)reconnection
{
//    if (_socketCode == 200) {
//        //重连peer
//        NSLog(@"重连~~~~~~~~~~~媒体");
//        [WHToast showMessage:@"媒体重连中..."];
//        [self reconnectionPeerConnection];
//    }else{
//        NSLog(@"重连~~~~~~~~~~~信令");
//        [WHToast showMessage:@"信令重连中..."];
//        __weak __typeof(self) ws = self;
//        [self connectSocketComplete:^(BOOL status) {
//            if (!status) {
//                return;
//            }
//            [WHToast showMessage:@"信令重连成功"];
//            //发送加入房间信令
//            [ws.mKLClient sendJoin:ws.roomId info:@{@"name" : ws.name}];
//            //重连peer
//            [ws reconnectionPeerConnection];
//        }];
//    }
    
    [WHToast showMessage:@"信令重连中..."];
    __weak __typeof(self) ws = self;
    [self connectSocketComplete:^(BOOL status) {
        if (!status) {
            return;
        }
        [WHToast showMessage:@"信令重连成功"];
        //发送加入房间信令
        [ws.mKLClient sendJoin:ws.roomId info:@{@"name" : ws.name}];
        //重连peer
        [ws reconnectionPeerConnection];
    }];
}

/**重新连接peer*/
- (void)reconnectionPeerConnection
{
    if (!_mKLPeerLocal) {
        _mKLPeerLocal = [[KLPeerLocal alloc] initWithClient:self.mKLClient factory:self.mPeerConnectionFactory iceServers:self.iceServers];
        _mKLPeerLocal.roomId = self.roomId;
        _mKLPeerLocal.delegate = self;
        [_mKLPeerLocal freePeerConnection];
        [_mKLPeerLocal initCapturer];
    }
    [_mKLPeerLocal initPeerConnection];
    [_mKLPeerLocal createOffer];
}

#pragma mark - Getter

- (RTCPeerConnectionFactory *)mPeerConnectionFactory
{
    if (!_mPeerConnectionFactory) {
        RTCDefaultVideoEncoderFactory *encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];
        RTCDefaultVideoDecoderFactory *decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];
        NSArray *encodecs = [encoderFactory supportedCodecs];
        [encoderFactory setPreferredCodec:encodecs[0]];
        _mPeerConnectionFactory = [[RTCPeerConnectionFactory alloc] initWithEncoderFactory:encoderFactory decoderFactory:decoderFactory];
    }
    return _mPeerConnectionFactory;
}

- (NSMutableArray<RTCIceServer *> *)iceServers
{
    if (!_iceServers) {
        _iceServers = [NSMutableArray array];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"stun:120.238.78.214:3478"]]];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"turn:120.238.78.214:3478?transport=tcp"] username:@"demo" credential:@"123456"]];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"turn:120.238.78.214:3478?transport=udp"] username:@"demo" credential:@"123456"]];
    }
    return _iceServers;
}

- (KLClient *)mKLClient
{
    if (!_mKLClient) {
        _mKLClient = [[KLClient alloc] init];
        _mKLClient.delegate = self;
        __weak __typeof(self) ws = self;
        _mKLClient.eventHandleBlock = ^(NSDictionary * _Nonnull response) {
            if (ws.eventHandleBlock) {
                ws.eventHandleBlock(response);
            }
        };
        //在此监听socket连接状态 如果断开判断先判断网络是否可用 如果可用则开启重连
        _mKLClient.socketStatusBlock = ^(NSInteger code) {
            ws.socketCode = code;
            if (ws.netStatus == ReachableViaWWAN || ws.netStatus == ReachableViaWiFi) {
                if (code == 201 && !self->_bCloseMeet) {
                    [ws startTimer];
                }else{
                    [ws stopTimer];
                }
            }else{
                [ws stopTimer];
            }
        };
    }
    return _mKLClient;
}

- (NSMutableArray<KLPeerRemote *> *)mKLPeerRemotes
{
    if (!_mKLPeerRemotes) {
        _mKLPeerRemotes = [NSMutableArray array];
    }
    return _mKLPeerRemotes;
}

- (NSMutableDictionary<NSString *,RTCVideoTrack *> *)remoteVideoTracks{
    if (!_remoteVideoTracks) {
        _remoteVideoTracks = [NSMutableDictionary dictionary];
    }
    return _remoteVideoTracks;
}

@end
